<?php
/**
 * Copyright 2011  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * Repository UI
 * 
 * Methods used for displaying data.
 */
class escape_repositoryui_RepositoryUi
{
	/**
	 * Repository instance.
	 * @var escape_repository_Repository
	 */
	public $repository;
	
	/**
	 * Current user, null if no user is logged on.
	 * @var escape_repository_User
	 */
	public $user = null;
	
	
	protected $gadgetMode = false;
	
	protected $requestUniqueIdCounter = 0;

	public $templateId = 'default';
	
	public function __construct(&$repository)
	{
		$this->repository =& $repository;
		
		if($this->repository->config['default_template'])
		{
			$this->setTemplate($this->repository->config['default_template']);
		}
		
		@session_start();
		
		if(isset($_SESSION['user_uri']) && $_SESSION['user_uri'] != '')
		{
			$userUri = $_SESSION['user_uri'];
			
			$user = null;
			try
			{
				$user = $this->repository->getUserByUri($userUri);
			}
			catch(escape_repository_exception_NotFound $exception)
			{
				//ignore
			}
			
			if(!$user)
			{
				// the logged on user does not exist!
				// assume that the account was deleted and wipe the userPid from the session 
				unset($_SESSION['user_uri']);
			}
			else
			{
				// the logged on user exists, keep a reference to it
				$this->user =& $user;
			}
		}
	}
	
	/**
	 * Enable the gadget mode.
	 */
	public function enableGadgetMode()
	{
		$this->setTemplate('gadget');
		$this->gadgetMode = true;
	}
	
	/**
	 * Get the gadget mode.
	 * @return boolean True if gadget mode is disabled
	 */
	public function isGadgetMode()
	{
		return $this->gadgetMode;
	}
	
	public function redirectToLoginForUrl($returnUrl)
	{
		if($this->isGadgetMode())
		{
			if(!preg_match('/[?&]gadget=1/', $returnUrl))
			{
				if(strpos($returnUrl, '?'))
				{
					$returnUrl .= '&gadget=1';
				}
				else
				{
					$returnUrl .= '?gadget=1';
				}
			}
		}
		
		$loginUrl = $this->repository->config['server_url'] . '/login?return_url=' . rawurlencode($returnUrl);
		
		if($this->isGadgetMode())
		{
			$loginUrl .= '&gadget=1';
		}
		
		header("Location: " . $loginUrl);
	}
	
	public function redirectToLoginForAction($action, $objectId = null)
	{
		$returnUrl = $this->getActionLinkById($action, $objectId);
		$this->redirectToLoginForUrl($returnUrl);
	}
	
	
	/**
	 * Set the template by ID.
	 * @param string $templateId Template ID
	 */
	public function setTemplate($templateId)
	{
		if($templateId !== null && $templateId !== "" && !$this->isGadgetMode())
		{
			$templateId = str_replace('/', '', $templateId);
			$templateId = str_replace('\\', '', $templateId);
			
			if(file_exists('templates/' . $templateId . '/'))
			{
				$this->templateId = $templateId;
			}
		}
	}
	
	/**
	 * Set the right template based on the aggregation provided.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 */
	public function setTemplateFromAggregation(&$aggregation)
	{
		$rdfResource = $aggregation->getProperties();
		$templateId = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#template');
		$this->setTemplate($templateId);
	}
	
	/**
	 * Get a list of available templates.
	 * @return array List of templates, each item is an array with keys 'id' and 'name'
	 */
	public function getTemplateList()
	{
		$templates = array();
		
		$templatesDirectory = opendir('templates');
		while($entryName = readdir($templatesDirectory))
		{
			$template = $this->getTemplate($entryName);
				
			if($template !== null && !$template['disabled'])
			{
				$templates[$template['id']] = $template;
			}
		}
		closedir($templatesDirectory);
		
		uasort($templates, array($this, '_compareTemplates'));
		
		return $templates;
	}
	/**
	 * Get a template by ID.
	 * TODO: return an object instead of an array???
	 * @param string $templateId Template ID
	 * @return array Array with keys 'id' and 'name'
	 */
	public function getTemplate($templateId)
	{
		$template = null;
		
		if(preg_match('/^[a-zA-Z_-]+$/', $templateId))
		{
			if(file_exists('templates/' . $templateId))
			{
				$template = array();
				$template['name'] = $templateId;
				
				$iniFilePath = 'templates/' . $templateId . '/template.ini';
				if(file_exists($iniFilePath))
				{
					$template = parse_ini_file($iniFilePath);
				}
		
				$template['id'] = $templateId;
			}
		}
		
		return $template;
	}
	
	public function _compareTemplates($templateA, $templateB)
	{
		$a = $templateA['name'];
		$b = $templateB['name'];
		
		if ($a == $b) {
			return 0;
		}
		return ($a < $b) ? -1 : 1;
	}

	/**
	 * Display the login status.
	 */
	public function displayLoginStatus()
	{
		if($this->user)
		{
			// do not show logout link + username in gadget mode if only SAML is enabled
			if($this->isGadgetMode() && !$this->repository->config['localauthn_enabled'] && $this->repository->config['saml']['enabled'])
			{
				// show a Back to MyESCAPE link only if the current page isn't MyESCAPE
				if(!preg_match('~^/me(\\?.*)?$~', $_SERVER['REQUEST_URI']))
				{
?>
 <a href="/me?gadget=1" title="Go to the My ESCAPE page">&lt; Back to My ESCAPE</a>
<?php
				}
			}
			else
			{
				if($this->isGadgetMode())
				{
?>
Logged on as: <?php print htmlspecialchars($this->user->getName()); ?>
 (<a href="/me?gadget=1" title="Go to the My ESCAPE page">My ESCAPE</a> | <a href="#" onclick="$(this).setAttribute('href', '/logout?gadget=1&amp;return_url=' + encodeURIComponent(location.href))">Log out</a>)
<?php
				}
				else
				{
?>
Logged on as: <?php print htmlspecialchars($this->user->getName()); ?>
 (<a href="/me" title="Go to the My ESCAPE page">My ESCAPE</a> | <a href="#" onclick="$(this).setAttribute('href', '/logout?return_url=' + encodeURIComponent(location.href))">Log out</a>)
<?php
				}
			}
		}
		else
		{
			if(!preg_match('~^/(login|signup)~', $_SERVER['REQUEST_URI']))
			{
				if($this->isGadgetMode())
				{
?>
<a href="#" onclick="$(this).setAttribute('href', '/login?gadget=1&amp;return_url=' + encodeURIComponent(location.href))">Log in</a>
<?php
				}
				else
				{
?>
<a href="#" onclick="$(this).setAttribute('href', '/login?return_url=' + encodeURIComponent(location.href))">Log in</a>
<?php
				}
			}
		}
	}
	
	/**
	 * Get the list of breadcrumbs for this session.
	 * @return array Breadcrumbs
	 */
	public function getBreadCrumbs()
	{
		if(!isset($_SESSION['breadcrumbs']))
		{
			$_SESSION['breadcrumbs'] = array();
		}
		
		return $_SESSION['breadcrumbs'];
	}
	
	/**
	 * Add a breadcrumb to this session.
	 * @param escape_repositoryui_BreadCrumb $breadCrumb Breadcrumb
	 */
	public function addBreadCrumb($breadCrumb)
	{
		if(!isset($_SESSION['breadcrumbs']))
		{
			$_SESSION['breadcrumbs'] = array();
		}
		
		// don't add consecutive duplicate breadcrumbs 
		$pos = array_search($breadCrumb, $_SESSION['breadcrumbs']);
		if($pos !== false)
		{
			unset($_SESSION['breadcrumbs'][$pos]);
			$_SESSION['breadcrumbs'] = array_values($_SESSION['breadcrumbs']);
		}
		
		// add the breadcrumb
		$_SESSION['breadcrumbs'][] = $breadCrumb;
		
		// limit number of breadcrumbs
		if(count($_SESSION['breadcrumbs']) > $this->repository->config['breadcrumbs_num'])
		{
			array_shift($_SESSION['breadcrumbs']);
		}
	}
	
	/**
	 * Clear the session breadcrumbs.
	 */
	public function clearBreadCrumbs()
	{
		$_SESSION['breadcrumbs'] = array();
	}
	
	/**
	 * Get the request user.
	 * @return escape_repository_User User
	 */
	public function &getUser()
	{
		return $this->user;
	}
	
	/**
	 * Create a URL to an object action.
	 * @param string $action Action (show/edit/resource/rem/aggregation)
	 * @param string $uri Object URI
	 * @return string Link URL
	 */
	public function getActionLinkByUri($action, $uri)
	{
		return $this->getActionLinkById($action, $this->repository->convertObjectUriToDataObjectId($uri));
	}
	
	/**
	 * Create a URL to an object action.
	 * @param string $action Action (show/edit/resource/rem/aggregation)
	 * @param string $id Object ID
	 * @return string Link URL
	 */
	public function getActionLinkById($action, $id = null)
	{
		$action = strtolower(substr($action, 0, 1)) . substr($action, 1);
		if($id !== null)
		{
			return '/' . rawurlencode($action) . '/' . rawurlencode($id);
		}
		else
		{
			return '/' . rawurlencode($action);
		}
	}
	
	/**
	 * Display the page header.
	 * @param array $templateVariables Template variables (title, head_html)
	 */
	public function displayPageHeader($templateVariables)
	{
		$templateVariables['head_html'] = '
<!-- begin ESCAPE includes -->
<script type="text/javascript" src="/rsc/js/prototype-1.7.0.0.js"></script>

<script type="text/javascript" src="/rsc/js/tooltip-v0.2.js"></script>

<link rel="stylesheet" type="text/css" href="/rsc/js/dhtmlxCalendar/dhtmlxcalendar.css" />
<script type="text/javascript" src="/rsc/js/dhtmlxCalendar/dhtmlxcommon.js"></script>
<script type="text/javascript" src="/rsc/js/dhtmlxCalendar/dhtmlxcalendar.js"></script>
<script type="text/javascript">window.dhx_globalImgPath="rsc/js/dhtmlxCalendar/imgs/";</script>

<!--[if lte IE 6]>
<script type="text/javascript" src="/rsc/js/iepngfix/iepngfix_tilebg.js"></script>
<link rel="stylesheet" href="/rsc/css/escape_ie_lte_6.css?v1" type="text/css" />
<![endif]-->

<script type="text/javascript" src="/rsc/js/jquery-1.6.3.min.js"></script>

<link rel="stylesheet" href="/rsc/js/DataTables-1.8.2/media/css/demo_table.css" type="text/css" />
<script type="text/javascript" src="/rsc/js/DataTables-1.8.2/media/js/jquery.dataTables.min.js"></script>

<script type="text/javascript">$.noConflict();</script>

<script type="text/javascript" src="/rsc/js/escape.js?v2"></script>
<link rel="stylesheet" href="/rsc/css/escape.css?v2" type="text/css" />

<script type="text/javascript">escape_escapeUi.gadgetMode = ' . ($this->isGadgetMode() ? 'true' : 'false') . ';</script> 
<!-- end ESCAPE includes -->
' . $templateVariables['head_html'];
		
		require_once('templates/' . $this->templateId . '/header.inc.php');
?>
<div id="escape_Body">
<?php
	}
	
	/**
	 * Display the page footer.
	 * @param array $templateVariables Template variables
	 */
	public function displayPageFooter($templateVariables)
	{
?>
</div>
<?php
		require_once('templates/' . $this->templateId . '/footer.inc.php');
	}

	public function displayErrorPage($message)
	{
		$templateVariables = array();
		$templateVariables['title'] = 'Error';
		
		$this->displayPageHeader($templateVariables);
		
		print '<div class="escape_Selection">';
		print '<h3>Error</h3>';
		print '<pre class="escape_PreWrap">';
		print htmlspecialchars($message);
		print '</pre>';
		print '</div>';
		
		$this->displayPageFooter($templateVariables);
	}
		
	/**
	 * Get a unique ID (mostly used for HTML DOM IDs).
	 * @return string Unique ID
	 */
	public function getUniqueId()
	{
		return 'uid' . dechex(mt_rand(0, 0x7FFFFFFF));
	}
	
	/**
	 * Display a statement.
	 * @param string $subject Statement subject
	 * @param string $predicate Statement predicate
	 * @param string $object Statement object
	 */
	public function displayPropertyValue($subject, $predicate, $object)
	{
		$gadgetModeBlankTarget = '';
		if($this->isGadgetMode())
		{
			$gadgetModeBlankTarget = 'target="_blank"';
		}
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}
				
		if(preg_match('~^info:fedora/' . $this->repository->config['data_object_namespace'] . ':~', $object))
		{
			//TODO add try catch
			$aggregatedResource = $this->repository->getOreAggregatedResourceByUri($object);
			$title = $aggregatedResource->getTitle();
			$link = '/show/' . $aggregatedResource->getId();
			print '<a href="' . htmlspecialchars($link) . $gadgetModeUrlParameter . '">' . htmlspecialchars($title) . '</a>';
		}
		else
		{
			$rangeUri = $this->repository->repositorySchema->getFirstRangeUri($predicate);
			switch($rangeUri):
				case 'http://www.w3.org/2001/XMLSchema#date':
					// date
					if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $object))
					{
						print htmlspecialchars(strftime("%B %e, %Y", strtotime($object)));
					}
					else if(preg_match('/^[0-9]{4}-[0-9]{2}$/', $object))
					{
						print htmlspecialchars(strftime("%B, %Y", strtotime($object . '-01')));
					}
					else if(preg_match('/^[0-9]{4}$/', $object))
					{
						print $object;
					}
					else
					{
						print htmlspecialchars($object);
					}
					break;
				case 'http://www.w3.org/2001/XMLSchema#dateTime':
					// date + time
					print htmlspecialchars(strftime("%B %e, %Y %T", strtotime($object)));
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#mailtoURL':
				case 'http://purl.utwente.nl/ns/escape-system.owl#telURL':
				case 'http://www.w3.org/2001/XMLSchema#anyURI':
					// URIs
					$urlParts = explode(':', $object, 2);
					if($urlParts[0] === 'mailto')
					{
						print '<a href="' . htmlspecialchars($object) . '" ' . $gadgetModeBlankTarget . '>' . htmlspecialchars($urlParts[1]) . '</a>';
					}
					else if($urlParts[0] === 'tel')
					{
						print htmlspecialchars($urlParts[1]);
					}
					else
					{
						print '<a href="' . htmlspecialchars($object) . '" ' . $gadgetModeBlankTarget . '>' . htmlspecialchars($object) . '</a>';
					}
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#imageURL':
					// image URL
					print '<a href="' . htmlspecialchars($object) . '" ' . $gadgetModeBlankTarget . '><img src="' . htmlspecialchars($object) . '" style="max-height:100px; width:auto;" onload="if(typeof this.style.maxHeight == \'undefined\' && this.height > 100) this.style.height=\'100px\';" alt="" border="0" /></a>';
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#schemaResourceURI':
					// schema resource URI
					$predicateLabel = $this->repository->repositorySchema->getResourceLabel($object);
					print htmlspecialchars($predicateLabel);
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#templateID':
					$templates = $this->getTemplateList();
					
					$currentTemplateName = $object;
					if(isset($templates[$object]))
					{
						$currentTemplateName = $templates[$object]['name'];
					}
					
					print htmlspecialchars($currentTemplateName);
					
					break;
				default:
					// 
					if(preg_match('~^(http|https|ftp|ftps):~', $object))
					{
						print '<a href="' . htmlspecialchars($object) . '">' . htmlspecialchars($object) . '</a>';
					}
					else
					{
						print str_replace("\n", "<br />\n", htmlspecialchars($object));
					}
			endswitch;
		}
	}
	
	/**
	 * Display an ORE object editor fragment for one single statement.
	 * @param string $containerElementId DOM ID of the HTML element containing this fragment
	 * @param string $subject Statement subject
	 * @param string $predicate Statement predicate
	 * @param string $object Statement object
	 * @param string $mode Mode, 'show' = show statement field, 'edit' = edit existing statement field, 'new' = add new statement field
	 * @param boolean $single true = field has cardinality of 1
	 */
	public function displayOreObjectEditorPropertyValueFragment($containerElementId, $subject, $predicate, $object, $mode, $single)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}

		$relationAnnotations = array();
		if($mode !== 'new' && $object !== null)
		{
			$relationAnnotations = $this->repository->getRelationAnnotationsBySubjectPredicateObject($subject, $predicate, $object);
		}
		
		$saveOnClick = '';
		$removeOnClick = '';
		$cancelOnClick = '';

		$rangeUri = $this->repository->repositorySchema->getFirstRangeUri($predicate);
?>
<div class="value">
<form name="property_value_form_<?php print $containerElementId; ?>" onsubmit="return false" action="#">
<?php
		if($mode === 'show')
		{
			if(count($relationAnnotations) > 0)
			{
?>
<div style="float: right; font-weight: bold; font-size: 85%">
<?php
				foreach($relationAnnotations as $relationAnnotation)
				{
			
?>
<div>[<a href="/show/<?php print htmlspecialchars($relationAnnotation->getId()) . $gadgetModeUrlParameter; ?>">Show relation&nbsp;annotation</a>]</div>
<?php
				}
?>
</div>
<?php
			}
			else
			{
				if(preg_match('~^info:fedora/~', $object))
				{					
?>
<div style="float: right">
<div><button onclick="this.disabled = true; escape_escapeUi.doRpcJsonRequest('CreateRelationAnnotation', {aggregationUri: oreObjectEditorAggregationUri, subjectUri: <?php print htmlspecialchars(json_encode($subject));?>, predicateUri: <?php print htmlspecialchars(json_encode($predicate));?>, objectUri: <?php print htmlspecialchars(json_encode($object));?>}, escape_escapeUi.redirectToResourceByUri.curry('edit')); return false">Annotate</button></div>
</div>
<?php
				}
			}
?>

<button class="oreObjectEditorPropertyValueFragmentButton" onclick="this.disabled = true; getOreObjectEditorPropertyValueFragment('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($subject)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($predicate)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($object)); ?>', 'edit', <?php print json_encode($single); ?>) ; return false"><img src="/rsc/img/edit.gif" alt="Edit" /></button>

<?php
		}
		
		if($mode === 'edit' || $mode === 'new')
		{
			switch($rangeUri):
				case 'http://www.w3.org/2001/XMLSchema#date':
					// date
?>
<input type="text" size="15" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars($object);?>" /> <em>(yyyy-mm-dd)</em>
<script type="text/javascript">
	mCal = new dhtmlxCalendarObject('<?php print escape_javascriptEscape('value_' . $containerElementId); ?>', false, {isYearEditable: true, isMonthEditable: true, isWinHeader: false, headerButtons: "XMT"});
	mCal.setSkin("simplegrey");
	mCal.draw();

	dhtmlxCalendarObjects['<?php print escape_javascriptEscape('value_' . $containerElementId); ?>'] = mCal;
</script>
<br />
<?php
					$saveOnClick = "dhtmlxCalendarObjects['" . escape_javascriptEscape('value_' . $containerElementId) . "'].hide();";
					$removeOnClick = "dhtmlxCalendarObjects['" . escape_javascriptEscape('value_' . $containerElementId) . "'].hide();";
					$cancelOnClick = "dhtmlxCalendarObjects['" . escape_javascriptEscape('value_' . $containerElementId) . "'].hide();";
					break;
				case 'http://www.w3.org/2001/XMLSchema#dateTime':
					// date + time
					print htmlspecialchars(strftime("%B %e, %Y %T", strtotime($object)));
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#mailtoURL':
					$saveOnClick = "var valueElement = $(" . json_encode('value_' . $containerElementId) . "); if(!valueElement.value.match(/^mailto:/)) { valueElement.value = 'mailto:' + valueElement.value } ";
?>
<input type="text" size="50" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars(str_replace('mailto:', '', $object));?>" />
<br />
<span class="escape_Note">For example: j.doe@somedomain.com</span>
<br />
<?php
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#telURL':
					$saveOnClick = "var valueElement = $(" . json_encode('value_' . $containerElementId) . "); if(!valueElement.value.match(/^tel:/)) { valueElement.value = 'tel:' + valueElement.value } valueElement.value = valueElement.value.replace(/ /g, ''); ";
?>
<input type="text" size="50" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars(str_replace('tel:', '', $object));?>" />
<br />
<span class="escape_Note">For example: 0031201234567</span>
<br />
<?php
					break;
				case 'http://www.w3.org/2001/XMLSchema#anyURI':
?>
<input type="text" size="50" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars($object);?>" />
<br />
<span class="escape_Note">For example: http://www.google.com/</span>
<br />
<?php
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#imageURL':
?>
<input type="text" size="50" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars($object);?>" />
<br />
<span class="escape_Note">For example: http://www.mywebsite.com/myimage.jpg</span>
<br />
<?php
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#schemaResourceURI':
					// schema resource URI
					$predicateLabel = $this->repository->repositorySchema->getResourceLabel($object);
					print htmlspecialchars($predicateLabel);
					break;
				case 'http://purl.utwente.nl/ns/escape-system.owl#templateID':
					$templates = $this->getTemplateList();
					
					$currentTemplateName = $object;
					if(isset($templates[$object]))
					{
						$currentTemplateName = $templates[$object]['name'];
					}
?>
<input type="hidden" name="value" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars($object);?>" />
<div id="object_creator_<?php print $containerElementId; ?>">
Current selection: <strong><span id="selected_value_label_<?php print $containerElementId; ?>"><?php print htmlspecialchars($currentTemplateName);?></span></strong>
<ul>
<?php
					foreach($templates as $template)
					{
?>
<li><a href="#" onclick="$('<?php print 'value_' . $containerElementId; ?>').value = '<?php print htmlspecialchars(escape_javascriptEscape($template['id']));?>'; $('<?php print 'selected_value_label_' . $containerElementId; ?>').innerHTML = '<em><?php print escape_javascriptEscape(htmlspecialchars($template['name']));?></em>'; return false"><?php print htmlspecialchars($template['name']);?></a></li>
<?php
					}
?>
</ul>
<p class="escape_Note">Click "show" after saving the new template selection to view the new template.</p>
</div>
<?php
					break;
				default:
					// check if this is an aggregation
					$isAggregation = $this->repository->isOreAggregation($subject);
			
					$range = $this->repository->repositorySchema->getResourceBySubject($rangeUri);
					if($isAggregation || $range === null || ($mode === 'edit' && !preg_match('~^info:fedora/' . $this->repository->config['data_object_namespace'] . ':~', $object)))
					{
						// aggregation, unknown type or not an object link, fall back to text editor
?>
<textarea rows="1" cols="50" id="value_<?php print $containerElementId; ?>"><?php print htmlspecialchars($object);?></textarea>
<script type="text/javascript">
(new escape_escapeUi.ResizingTextArea($('value_<?php print $containerElementId; ?>'))).resize();
$('value_<?php print $containerElementId; ?>').focus();
</script>
<br />
<?php
					}
					else
					{
						$rangeUris = $this->repository->repositorySchema->getRangeUris($predicate);
						
						$classUris = array();
						foreach($rangeUris as $rangeUri)
						{
							$classUris[] = $rangeUri;
							
							$subClassUris = $this->repository->repositorySchema->getSubClassUriList($rangeUri);
							foreach($subClassUris as $subClassUri)
							{
								$classUris[] = $subClassUri;
							}
						}

						$subjectOreObject = $this->repository->getOreObjectByUri($subject);
						$aggregation = $subjectOreObject->getAggregation();
						$matchingResources = $aggregation->getAggregatedResources($classUris);

						$matchingResourceTitlesByUri = array();
						foreach($matchingResources as $matchingResource)
						{
							$matchingResourceUri = $matchingResource->getUri();
							$matchingResourceTitle = $matchingResource->getTitle();
							
							$matchingResourceTitlesByUri[$matchingResourceUri] = $matchingResourceTitle;
						}
						asort($matchingResourceTitlesByUri);
						
						$objectTitle = '';
						if($object !== null && $object !== '')
						{
							$objectAggregatedResource = $this->repository->getOreAggregatedResourceByUri($object);
							$objectTitle= $objectAggregatedResource->getTitle();
						}
						
?>
<input type="hidden" name="value" id="value_<?php print $containerElementId; ?>" value="<?php print htmlspecialchars($object);?>" />
<div id="object_creator_<?php print $containerElementId; ?>">
Current selection: <strong><span id="selected_value_label_<?php print $containerElementId; ?>"><?php print htmlspecialchars($objectTitle);?></span></strong>
<br />
<br />
<a href="#" onclick="new escape_escapeUi.OreAggregatedResourceCreator($(<?php print htmlspecialchars(json_encode('object_creator_' . $containerElementId)); ?>), <?php print htmlspecialchars(json_encode($rangeUris)); ?>); return false">Create a new object</a>
<br />
<br />
Or choose an existing object:
<ul>
<?php
						foreach($matchingResourceTitlesByUri as $matchingResourceUri => $matchingResourceTitle)
						{
?>
<li><a href="#" onclick="$('<?php print 'value_' . $containerElementId; ?>').value = '<?php print htmlspecialchars(escape_javascriptEscape($matchingResourceUri));?>'; $('<?php print 'selected_value_label_' . $containerElementId; ?>').innerHTML = '<em><?php print escape_javascriptEscape(htmlspecialchars($matchingResourceTitle));?></em>'; return false"><?php print htmlspecialchars($matchingResourceTitle);?></a></li>
<?php
						}
?>
</ul>
</div>
<?php
					}
			endswitch;
		}
		else
		{
			$this->displayPropertyValue($subject, $predicate, $object);
		}

		if($mode === 'edit')
		{
?>
<button name="save" class="oreObjectEditorPropertyValueFragmentButton" onclick="<?php print htmlspecialchars($saveOnClick);?> this.disabled = true; updateOreObjectPropertyValue('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($subject)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($predicate)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($object)); ?>', $F('value_<?php print $containerElementId; ?>'), <?php print json_encode($single); ?>); return false">Save</button>
<?php
			if(!$single)
			{
?>
<button name="remove" class="oreObjectEditorPropertyValueFragmentButton" onclick="<?php print htmlspecialchars($removeOnClick);?> this.disabled = true; removeOreObjectPropertyValue('<?php print htmlspecialchars(escape_javascriptEscape($subject)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($predicate)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($object)); ?>'); $('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>').remove(); return false">Remove</button>
<?php
			}
?>
<button name="cancel" class="oreObjectEditorPropertyValueFragmentButton" onclick="<?php print htmlspecialchars($cancelOnClick);?> this.disabled = true; getOreObjectEditorPropertyValueFragment('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($subject)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($predicate)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($object)); ?>', 'show', <?php print json_encode($single); ?>) ; return false">Cancel</button>

<?php
		}
		else if($mode === 'new')
		{
?>
<button name="save" class="oreObjectEditorPropertyValueFragmentButton" onclick="<?php print htmlspecialchars($saveOnClick);?> this.disabled = true; addOreObjectPropertyValue('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($subject)); ?>', '<?php print htmlspecialchars(escape_javascriptEscape($predicate)); ?>', $F('value_<?php print $containerElementId; ?>')); return false">Save</button>
<button name="cancel" class="oreObjectEditorPropertyValueFragmentButton" onclick="<?php print htmlspecialchars($cancelOnClick);?> this.disabled = true; $('<?php print htmlspecialchars(escape_javascriptEscape($containerElementId)); ?>').remove(); return false">Cancel</button>

<?php
		}
?>
</form>
</div>
<?php
	}

	/**
	 * Display an ORE object editor fragment for a single predicate.
	 * @param string $subject Statement subject
	 * @param string $predicate Statement predicate
	 * @param string $object Statement object
	 * @param string $mode Mode, 'show' = show statement field, 'edit' = edit existing statement field, 'new' = add new statement field
	 * @param boolean $single True = field has cardinality of 1
	 */
	public function displayOreObjectEditorPropertyFragment($subject, $predicate, $objects, $mode, $single = false)
	{
		$repositorySchema =& $this->repository->repositorySchema;
		
		if($objects === null)
		{
			$objects = array();
		}

		if($single && count($objects) === 0)
		{
			$objects[] = null;
		}
		
		$label = $repositorySchema->getResourceLabel($predicate);
		$comment = $repositorySchema->getResourceComment($predicate);
		print '<th title="' . htmlspecialchars($predicate) . '">';
		if($comment)
		{
			print '<div style="float: right; margin-top: 3px">';
			$this->displayInfoTooltip($comment);
			print '</div>';
		}
		print htmlspecialchars($label);
		print '</th>';
		print '<td>';
		
		foreach($objects as $object)
		{
			$containerElementId = $this->getUniqueId();
			print '<div class="oreObjectEditorPropertyValueFragmentContainer" id="' . $containerElementId . '">';
			$this->displayOreObjectEditorPropertyValueFragment($containerElementId, $subject, $predicate, $object, 'show', $single);
			print '</div>';
		}

		if($mode === 'new')
		{
			$containerElementId = $this->getUniqueId();
			print '<div class="oreObjectEditorPropertyValueFragmentContainer" id="' . $containerElementId . '">';
			$this->displayOreObjectEditorPropertyValueFragment($containerElementId, $subject, $predicate, '', 'new', $single);
			print '</div>';
		}

		if(!$single)
		{
			$containerElementId = $this->getUniqueId();
			print '<div class="oreObjectEditorPropertyValueFragmentContainer value" id="' . $containerElementId . '">';
			print '<button class="oreObjectEditorPropertyValueFragmentButton" onclick="addOreObjectEditorPropertyValueFragment(\'' . htmlspecialchars(escape_javascriptEscape($containerElementId)) . '\', \'' . htmlspecialchars(escape_javascriptEscape($subject)) . '\', \'' . htmlspecialchars(escape_javascriptEscape($predicate)) . '\'); return false;"><img src="/rsc/img/add.gif" alt="Add" /></button>';
			print '</div>';
		}

		print '</td>';
	}
	
	/**
	 * Display an ORE object editor fragment for ORE Object.
	 * @param escape_repository_OreObject $oreObject ORE object
	 */
	public function displayOreObjectEditor(&$oreObject)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}
		
		$repositorySchema =& $this->repository->repositorySchema;
		
		$aggregation = $oreObject->getAggregation();
		
		$rdfResource = $oreObject->getProperties();
	
		$isAggregation = $oreObject instanceof escape_repository_OreAggregation;
		
		$typeUri = $rdfResource->getFirstRdfType();
		$type = $repositorySchema->getResourceLabel($typeUri);
	//	$parentClassList = $repositorySchema->getNonSystemParentClassUriList($typeUri);
	//	foreach($parentClassList as $parentClass)
	//	{
	//		$type = $repositorySchema->getResourceLabel($parentClass) . ' > ' . $type;
	//	}

		$allowedClassPropertyListWithDetails = $repositorySchema->getAllowedClassPropertyListWithDetails($typeUri);
		
		$filteredObjectsByPredicate = array();

		foreach($allowedClassPropertyListWithDetails as $propertyUri => $propertyDetails)
		{
			if($propertyDetails['minCardinality'] > 0)
			{
				$filteredObjectsByPredicate[$propertyUri] = array();
			}
		}
		
		$predicates = $rdfResource->getPredicates();
		foreach($predicates as $predicate)
		{
			if(!$repositorySchema->isHiddenProperty($predicate))
			{
				$objects = $rdfResource->getObjects($predicate);
				foreach($objects as $object)
				{
//					if(!preg_match('~^info:fedora/' . $this->repository->config['data_object_namespace'] . ':~', $object))
//					{
						$filteredObjectsByPredicate[$predicate][] = $object;
//					}
				}
			}
		}

		$externalUri = $oreObject->getExternalUri();

		
		$attachmentMimeType = $oreObject->getAttachmentMimeType();
		$attachmentFileName = $oreObject->getAttachmentFileName();
?>
<div class="escape_Section">
<span class="escape_SectionTitle" title="<?php print htmlspecialchars($typeUri); ?>">Edit object</span>
<span class="escape_Actions">[
<a href="/show/<?php print rawurlencode($oreObject->getId()) . $gadgetModeUrlParameter; ?>">Show</a>
|
<a href="/delete/<?php print rawurlencode($oreObject->getId()) . $gadgetModeUrlParameter; ?>">Delete</a>
<?php
		if(!$isAggregation && $attachmentMimeType === null && $this->repository->config['upload_enabled'])
		{
?>
|
<a href="/upload/<?php print rawurlencode($oreObject->getId()) . $gadgetModeUrlParameter; ?>">Attach a file</a>
<?php
		}
?>
<?php
		// show aggregation clone link if this is an aggregation and the user is an administrator or creator and also owner of this aggregation
		if($isAggregation)
		{
			if($this->user->hasSystemRole(escape_repository_User::$role_administrator) || ($this->user->hasSystemRole(escape_repository_User::$role_creator) && $aggregation->hasOwner($this->user->getUri())))
			{
?>
|
<a href="/clone/<?php print rawurlencode($oreObject->getId()) . $gadgetModeUrlParameter; ?>">Clone</a>
<?php
			}
		}
?>
]</span>
</div>

<div class="escape_Section">
<div class="escape_Hint">
<a target="_blank" href="<?php print htmlspecialchars($this->getActionLinkById('editorManual'))?>">Manual for editors</a>
</div>
</div>

<div class="escape_Object">
<form action="/upload/<?php print rawurlencode($oreObject->getId()) . $gadgetModeUrlParameter; ?>" method="post">
<input id="upload_verb" type="hidden" name="verb" value="" />

<?php
		if($attachmentMimeType !== null)
		{
?>
<p>
Attached file: <strong><?php print htmlspecialchars($attachmentFileName); ?></strong> <em>(<?php print htmlspecialchars($attachmentMimeType); ?>)</em>
<?php
	 		if($this->repository->config['upload_enabled'])
	 		{
?>
 <span class="escape_Actions">[ <a target="_blank" href="/resource/<?php print $oreObject->getId(); ?>">View</a> | <a href="/upload/<?php print $oreObject->getId() . $gadgetModeUrlParameter; ?>">Replace</a> | <a href="#" onclick="var confirmed = confirm('Are you sure you want to delete this attachment?'); if(confirmed) { $('upload_verb').value='delete'; $('upload_verb').form.submit(); } return false">Delete</a> ]</span>
<?php
	 		}
?>
</p>
<?php
		}
?>

</form>

<?php
		if($oreObject->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation'))
		{
?>
<p>
<?php
			print htmlspecialchars($oreObject->getTitle());
?>
</p>
<?php
		}
?>

<table id="oreObjectEditorPropertiesTable" class="escape_Properties" width="100%" cellpadding="0" cellspacing="0">
	<colgroup>
		<col width="110" />
		<col />
	</colgroup>
	<tr>
		<th>
			Object type
		</th>
		<td>
			<?php print htmlspecialchars($type); ?>
<?php
		if($oreObject instanceof escape_repository_OreAggregatedResource && !$oreObject->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation'))
		{
?>
			<span class="escape_Actions">[<a href="<?php print $this->getActionLinkById('changeType', $oreObject->getId()) . $gadgetModeUrlParameter; ?>">Change</a>]</span>
<?php
		}
?>
		</td>
	</tr>
	
<?php		
		$subject = $oreObject->getUri();

		if($oreObject instanceof escape_repository_OreAggregatedResource && !$oreObject->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation')  && $attachmentMimeType === null)
		{
			$tableRowElementId = $this->getUniqueId();
			print '<tr id="' . htmlspecialchars($tableRowElementId) . '">';
			$this->displayOreObjectEditorPropertyFragment($subject, 'http://purl.utwente.nl/ns/escape-system.owl#externalUri', array($externalUri), 'show', true);
			print '</tr>';
		}

		if($oreObject instanceof escape_repository_OreAggregation)
		{
			$templateId = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#template');
			
			$tableRowElementId = $this->getUniqueId();
			print '<tr id="' . htmlspecialchars($tableRowElementId) . '">';
			if($templateId !== null)
			{
				$this->displayOreObjectEditorPropertyFragment($subject, 'http://purl.utwente.nl/ns/escape-system.owl#template', array($templateId), 'show', true);
			}
			else
			{
				$this->displayOreObjectEditorPropertyFragment($subject, 'http://purl.utwente.nl/ns/escape-system.owl#template', array(), 'show', true);
			}
			print '</tr>';
		}
		
		$predicateTableRowElementIdMappings = array();
		foreach($filteredObjectsByPredicate as $predicate => $objects)
		{
			$tableRowElementId = $this->getUniqueId();
			$predicateTableRowElementIdMappings[$predicate] = $tableRowElementId;
			print '<tr id="' . htmlspecialchars($tableRowElementId) . '">';
			$this->displayOreObjectEditorPropertyFragment($subject, $predicate, $objects, 'show', false);
			print '</tr>';
		}
?>
</table>
		
<script type="text/javascript">
predicateTableRowElementIdMappings = <?php print json_encode($predicateTableRowElementIdMappings); ?>;
oreObjectEditorSubjectUri = <?php print json_encode($subject); ?>;
oreObjectEditorAggregationUri = <?php print json_encode($aggregation->getUri()); ?>;
</script>

<div>
<a href="#" onclick="$('oreObjectEditorPropertySelector').toggle(); return false">Add new properties or relations</a>
</div>
<div id="oreObjectEditorPropertySelector" style="display: none; padding: 5px; background-color: #f8f8f8">
<div style="margin-top: 4px; margin-bottom: 4px">
Allowed properties / relations for this type:
</div>
<?php
		$this->displayPropertySelectorTrees($subject, $allowedClassPropertyListWithDetails, 'addOreObjectEditorPredicate');
?>
</div>
		
</div>
<?php
	}
	
	//TODO: move this to javascript, similar to OreAggregatedResourceCreator.displayClassSelector ?
	/**
	 * Display a list of selectable properties.
	 * @param string $subject Subject
	 * @param array $classPropertyListWithDetails List of class properties with details, generate using: $repositorySchema->getAllowedClassPropertyListWithDetails($typeUri)
	 * @param string $jsFunctionName Name of JS function to call when the user selects a property
	 */
	public function displayPropertySelectorTrees($subject, $classPropertyListWithDetails, $jsFunctionName)
	{
		$repositorySchema =& $this->repository->repositorySchema;
		
		$subPropertyUriTrees = array();
		
		foreach($classPropertyListWithDetails as $propertyUri => $propertyDetails)
		{
			$subPropertyUriTrees[$propertyUri] = $repositorySchema->getAssignableSubPropertyUriTree($propertyUri);
		}
		$this->displayPropertySelectorTreeRecursive($subject, $subPropertyUriTrees, $jsFunctionName, 0);
	}

	protected function displayPropertySelectorTreeRecursive($subject, $propertyUriTrees, $jsFunctionName, $level)
	{
		$repositorySchema =& $this->repository->repositorySchema;
		
		// fetch the property labels
		$propertyUriLabels = array();
		foreach($propertyUriTrees as $propertyUri => $subPropertyUriTree)
		{
			$propertyUriLabels[$propertyUri] = $repositorySchema->getResourceLabel($propertyUri);
		}
		
		// sort the properties by label
		asort($propertyUriLabels);
		
		if($level === 0)
		{
			print '<ul class="escape_NestingNoFirstLevelMarker">';
		}
		else if($level === 1)
		{
			print '<ul style="display: none">';
		}
		else
		{
			print '<ul>';
		}
		
		foreach($propertyUriLabels as $propertyUri => $propertyUriLabel)
		{
			$subPropertyUriTree = $propertyUriTrees[$propertyUri];
			
			print '<li>';
			
			if($level === 0)
			{
				if($subPropertyUriTree !== null)
				{
					print '<span title="Click to expand" onclick="$(this.parentNode).select(\'ul\')[0].toggle(); $(this).select(\'img\').invoke(\'toggle\');">';
					print '<img src="/rsc/img/expand.gif" style="vertical-align: top" alt="Expand" />';
					print '<img src="/rsc/img/collapse.gif" style="vertical-align: top; display: none" alt="Collapse" />';
				}
				else
				{
					print '<span>';
					print '<img src="/rsc/img/noexpand.gif" style="vertical-align: top" alt="" />';
				}
				print ' ';
			}
			else
			{
				print '<span>';
			}
			//TODO: show title="' . htmlspecialchars($propertyUri) . '" somewhere else?
			
			print htmlspecialchars($propertyUriLabel);
			print '</span>';

			print ' <span class="escape_Actions">[ ';
			print '<a href="#" onclick="' . htmlspecialchars(escape_javascriptEscape($jsFunctionName)) . '(\'' . htmlspecialchars(escape_javascriptEscape($subject)) . '\', \'' . htmlspecialchars(escape_javascriptEscape($propertyUri)) . '\', \'new\'); return false">Select</a>';
			
			$comment = $repositorySchema->getResourceComment($propertyUri);
			if($comment !== null)
			{
				$commentId = $this->getUniqueId();
				print ' | <a href="#" onclick="$(\'' . htmlspecialchars(escape_javascriptEscape($commentId)) . '\').toggle(); return false">About</a>';
			}

			print' ]</span>';

			if($comment !== null)
			{
				print '<div id="' . htmlspecialchars($commentId) . '" class="escape_Hint" style="display: none">' . htmlspecialchars($comment) . '</div>';
			}
			
			if($subPropertyUriTree !== null)
			{
				$this->displayPropertySelectorTreeRecursive($subject, $subPropertyUriTree, $jsFunctionName, $level + 1);
			}

			print '</li>';
		}
		print '</ul>';
	}
	
	/**
	 * Get a hierarchical list of selectable classes.
	 * @param array $classUris Class URIs (subclasses are automatically included)
	 * @return array Nested array structure, for each class: uri, label, comment, titleUri, titleLabel, titleComment and children nested in: children
	 */
	public function getClassSelectorTrees($classUris)
	{
		$repositorySchema =& $this->repository->repositorySchema;
		
		$result = array();
		
		foreach($classUris as $classUri)
		{
			$subClassUris = $repositorySchema->getSubClassUris($classUri);
			$parentClassUris = $repositorySchema->getParentClassUriList($classUri);
			
			if(!in_array('http://purl.utwente.nl/ns/escape-system.owl#Thing', $parentClassUris) || $repositorySchema->isSystemClass($classUri))
			{
				if(count($subClassUris) > 0)
				{
					$subClassTrees = $this->getClassSelectorTrees($subClassUris);
					foreach($subClassTrees as $subClassTree)
					{
						$result[] = $subClassTree;
					}
				}
			}
			else
			{
				$titlePropertyUri = null;
				$titlePropertyLabel = null;
				$titlePropertyComment = null;
				$titlePropertyUris = $repositorySchema->getClassTitlePropertyUriList($classUri);
				if(count($titlePropertyUris) > 0)
				{
					$titlePropertyUri = $titlePropertyUris[0];
					$titlePropertyLabel = $repositorySchema->getResourceLabel($titlePropertyUri);;
					$titlePropertyComment = $repositorySchema->getResourceComment($titlePropertyUri);
				}
				
				$classArray = array();
				$classArray['uri'] = $classUri;
				$classArray['label'] = $repositorySchema->getResourceLabel($classUri);
				$classArray['comment'] = $repositorySchema->getResourceComment($classUri);
				$classArray['titleUri'] = $titlePropertyUri;
				$classArray['titleLabel'] = $titlePropertyLabel;
				$classArray['titleComment'] = $titlePropertyComment;
				$classArray['children'] = $this->getClassSelectorTrees($subClassUris);
				$result[] = $classArray;
			}
		}
		
		usort($result, array('escape_repositoryui_RepositoryUi', '_compareClassSelectorTrees'));
		
		return $result;
	}
	
	public function _compareClassSelectorTrees(&$a, &$b)
	{
		return strcoll($a['label'], $b['label']);
	}
	
	/**
	 * Display class selector trees.
	 * @param array $classSelectorTrees Class selector trees
	 * @param string $linkUrl Link url for select links, the selected class URI will be postfixed to this URL
	 */
	public function displayClassSelectorTrees($classSelectorTrees, $linkUrl)
	{
?>
<ul>
<?php
		foreach($classSelectorTrees as $class)
		{
			$this->_displayClassSelectorTreeRecursive($class, $linkUrl);
		}
?>
</ul>
<?php
	}

	public function _displayClassSelectorTreeRecursive($class, $linkUrl)
	{
		$aboutId = $this->getUniqueId();

		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '&gadget=1';
		}
?>
<li>
<?php print htmlspecialchars($class['label'])?>
<span class="escape_Actions">
[
<a href="<?php print htmlspecialchars($linkUrl . rawurlencode($class['uri']) . $gadgetModeUrlParameter); ?>">Select</a>
<?php
		if($class['comment'])
		{
?>
| <a href="#" onclick="$(<?php print htmlspecialchars(json_encode($aboutId)); ?>).toggle()">About</a>
<?php
		}
?>
]
</span>
<?php
		if($class['comment'])
		{
?>
<div id="<?php print htmlspecialchars($aboutId); ?>" style="display: none; font-size: 85%; font-style: italic;"><?php print htmlspecialchars($class['comment']); ?></div>
<?php
		}

		$children = $class['children'];
		if($children)
		{
?>
<ul>
<?php
			foreach($children as $child)
			{
				$this->_displayClassSelectorTreeRecursive($child, $linkUrl);
			}
?>
</ul>
<?php
		}
?>
</li>
<?php
	}
	
	/**
	 * Display a list of users matching the query terms specified.
	 * @param string $query query terms
	 * @param int $limit limit
	 * @param string $onClickJsHandler JS function name, called when a user is selected
	 */
	public function displayFindUserResultList($query, $limit, $onClickJsHandler)
	{
		$limit = @intval($limit);
		
		$users = $this->repository->getUsersByQuery($query, $limit + 1);
		
		$limitExceeded = false;
		if(count($users) > $limit)
		{
			$limitExceeded = true;
			$users = array_slice($users, 0, $limit);
		}
		
		if(count($users) === 0)
		{
?>
<p>No matches found.</p>
<?php
		}
		else
		{
?>
<table class="escape_ListTable">
	<tr>
		<th></th>
		<th>Name</th>
		<th>Organization</th>
		<th>ID</th>
	</tr>
<?php
			foreach($users as $user)
			{
				/* @var $user escape_repository_User */
?>
	<tr>
		<td class="escape_Actions">
			<a href="#" onclick="return <?php print htmlspecialchars($onClickJsHandler)?>(<?php print htmlspecialchars(json_encode($user->getUri()))?>)">select</a>
		</td>
		<td><?php print htmlspecialchars($user->getName()); ?></td>
		<td><?php print htmlspecialchars($user->getOrganization()); ?></td>
		<td><?php print htmlspecialchars($user->getId()); ?></td>
	</tr>
<?php
			}
?>
</table>
<?php
			
			if($limitExceeded)
			{
?>
<p><em>Too many results found, please refine your query.</em></p>
<?php
			}
		}
	}
	
	/**
	 * Display an editor fragment to select aggregation editors and owners.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 * @param string $containerElementId DOM ID of the HTML element containing this fragment
	 */
	public function displayOreAggregationEditorListEditor(&$aggregation, $containerElementId)
	{
?>
<p><strong><?php print htmlspecialchars(ucfirst($this->repository->config['label_aggregation'])); ?> editors</strong></p>
<?php
		$editorUris = $aggregation->getEditors();
		asort($editorUris);
		foreach($editorUris as $editorUri)
		{
			$editorId = preg_replace('/.*:/', '', $editorUri);
			$editorName = $editorId;
			try {
				$editor = $this->repository->getUserByUri($editorUri);
				$editorId = $editor->getId();
				$editorName = $editor->getName();
			}
			catch(Exception $e)
			{
				// ignore
			}
?>
<form action="#" method="get" onsubmit="return false">
<input type="button" onclick="removeOreAggregationEditor(<?php print htmlspecialchars(json_encode($containerElementId)); ?>, <?php print htmlspecialchars(json_encode($aggregation->getUri())); ?>, <?php print htmlspecialchars(json_encode($editorUri)); ?>); return false" value="Remove" /> <?php print htmlspecialchars($editorId); ?> (<?php print htmlspecialchars($editorName); ?>)
</form> 
<?php
		}

?>

<form action="#" method="get" onsubmit="findUsers($('new_editor_query_result'), $F('new_editor_query'), 'addEditor'); return false">

<script type="text/javascript">
function addEditor(userUri)
{
	addOreAggregationEditor(<?php print json_encode($containerElementId); ?>, <?php print json_encode($aggregation->getUri()); ?>, userUri);
	return false;
}
</script>

<p>
Find a user by name and/or ID:<br/>
<input type="text" name="new_editor_query" id="new_editor_query" value="" />
<input type="submit" value="Search" />
</p>

<div id="new_editor_query_result">
</div>

</form>

<p><strong><?php print htmlspecialchars(ucfirst($this->repository->config['label_aggregation'])); ?> owners</strong></p>
<?php
		$ownerUris = $aggregation->getOwners();
		asort($ownerUris);
		foreach($ownerUris as $ownerUri)
		{
			$ownerId = preg_replace('/.*:/', '', $ownerUri);
			$ownerName = $ownerId;
			try {
				$owner = $this->repository->getUserByUri($ownerUri);
				$ownerId = $owner->getId();
				$ownerName = $owner->getName();
			}
			catch(Exception $e)
			{
				// ignore
			}
?>
<form action="#" method="get" onsubmit="return false">
<input type="button" onclick="removeOreAggregationOwner(<?php print htmlspecialchars(json_encode($containerElementId)); ?>, <?php print htmlspecialchars(json_encode($aggregation->getUri())); ?>, <?php print htmlspecialchars(json_encode($ownerUri)); ?>); return false" value="Remove" /> <?php print htmlspecialchars($ownerId); ?> (<?php print htmlspecialchars($ownerName); ?>) 
</form>
<?php
		}

?>
<form action="#" method="get" onsubmit="findUsers($('new_owner_query_result'), $F('new_owner_query'), 'addOwner'); return false">

<script type="text/javascript">
function addOwner(userUri)
{
	addOreAggregationOwner(<?php print json_encode($containerElementId); ?>, <?php print json_encode($aggregation->getUri()); ?>, userUri);
	return false;
}
</script>

<p>
Find a user by name and/or ID:<br />
<input type="text" name="new_owner_query" id="new_owner_query" value="" />
<input type="submit" value="Search" />
</p>

<div id="new_owner_query_result">
</div>

</form>
<?php
	}
	
	/**
	 * Display an ORE object.
	 * @param escape_repository_OreAggregation $aggregation The aggregation to which this object belongs
	 * @param escape_repository_OreObject $oreObject ORE Object
	 * @param boolean $isSelected True if this is a 'selected' object (the 'select' won't be shown) (default: false)
	 * @param string $displayMode 0 = show only 'properties', 1 = show only 'short view properties', 2 = show 'properties' and 'relations' (default: 0)  
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	public function displayOreObject(&$aggregation, &$oreObject, $isSelected = false, $displayMode = 0, $selectOnClickHandler = null)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}
		
		$isRelationAnnotation = $oreObject->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation');
		
		$rdfResource = $oreObject->getProperties();
	
		$titleTriple = $oreObject->getTitle(true);
		$title = null;
		$titlePredicate = null;
		if($titleTriple !== null)
		{
			$titlePredicate = $titleTriple['P'];
			$title = $titleTriple['o'];
		}
		
		$typeUri = $rdfResource->getFirstRdfType();
		$type = $this->repository->repositorySchema->getResourceLabel($typeUri);
	//	$parentClassList = $this->repository->repositorySchema->getNonSystemParentClassUriList($typeUri);
	//	foreach($parentClassList as $parentClass)
	//	{
	//		$type = $this->repository->repositorySchema->getResourceLabel($parentClass) . ' > ' . $type;
	//	}
		
		if($title === null)
		{
			$title = 'Unnamed object ' . $oreObject->getId();
		}
		

		$predicates = $rdfResource->getPredicates();
		
		$shortViewPropertyUris = $this->repository->repositorySchema->getClassShortViewPropertyUriList($typeUri);
		
		$filteredObjectsByPredicate = array();
		foreach($predicates as $predicate)
		{
			if($predicate !== $titlePredicate && !$this->repository->repositorySchema->isHiddenProperty($predicate))
			{
				if($displayMode === 1)
				{
					if(in_array($predicate, $shortViewPropertyUris))
					{
						$objects = $rdfResource->getObjects($predicate);
						foreach($objects as $object)
						{
							$filteredObjectsByPredicate[$predicate][] = $object;
						}
					}
				}
				else
				{
					$objects = $rdfResource->getObjects($predicate);
					foreach($objects as $object)
					{
						if($displayMode === 2 || !preg_match('~^info:fedora/' . $this->repository->config['data_object_namespace'] . ':~', $object))
						{
							$filteredObjectsByPredicate[$predicate][] = $object;
						}
					}
				}
			}
		}
		
		$actionLinks = array();
		if(!$isSelected)
		{
			if($selectOnClickHandler !== null)
			{
				$actionLinks[] = '<a href="/show/' . htmlspecialchars($oreObject->getId()) . $gadgetModeUrlParameter . '" onclick="' . htmlspecialchars($selectOnClickHandler) . '(' . htmlspecialchars(json_encode($oreObject->getId())) . '); return false">Select</a>';
			}
			else
			{
				$actionLinks[] = '<a href="/show/' . htmlspecialchars($oreObject->getId()) . $gadgetModeUrlParameter . '">Select</a>';
			}
		}
		
		$externalUri = $oreObject->getExternalUri();
		$resourceUri = $oreObject->getResourceUri();
		//TODO: replace strpos with preg_match ^
		if($externalUri !== $resourceUri || $oreObject->getAttachmentMimeType() !== null)
		{
			if($this->isGadgetMode())
			{
				$actionLinks[] = '<a target="_blank" href="' . htmlspecialchars($externalUri) . '">View</a>';
			}
			else
			{
				$actionLinks[] = '<a href="' . htmlspecialchars($externalUri) . '">View</a>';
			}
		}

		if($this->user)
		{
			$userUri = $this->user->getUri();

			if($aggregation->hasOwner($userUri) || $aggregation->hasEditor($userUri) || $this->user->hasSystemRole(escape_repository_User::$role_administrator))
			{
				$actionLinks[] = '<a href="/edit/' . htmlspecialchars($oreObject->getId()) . $gadgetModeUrlParameter . '">Edit</a>';
			}
		}
		
		print '<div class="escape_Object">';
		print '<div>';
		if($title !== null)
		{
			if($isRelationAnnotation)
			{
				print '<span class="escape_Title">';
				$this->displayRelationAnnotation($aggregation, $oreObject, $selectOnClickHandler, $isSelected);
				print '</span> ';
			}
			else
			{
				print '<span title="' . htmlspecialchars($titlePredicate) . '" class="escape_Title">' . str_replace("\n", "<br />\n", htmlspecialchars($title)) . '</span> ';
			}
		}
		if(count($actionLinks) > 0)
		{
			print '<span class="escape_Actions">[&nbsp;';
			print implode('&nbsp;|&nbsp;', $actionLinks);
			print '&nbsp;]</span>';
		}
		print '</div>';
		print '<div class="escape_Type"><span title="' . htmlspecialchars($typeUri) . '">';
		print htmlspecialchars($type);
		print '</span></div>';
		
		if(count($filteredObjectsByPredicate) > 0)
		{
			$subject = $oreObject->getUri();
			print '<table class="escape_Properties" border="0" cellspacing="1" cellpadding="0">';
			foreach($filteredObjectsByPredicate as $predicate => $objects)
			{
				$label = $this->repository->repositorySchema->getResourceLabel($predicate);
				
				foreach($objects as $object)
				{
					print '<tr>';
					print '<td valign="top" style="padding-right: 5pt" title="' . htmlspecialchars($predicate) . '">' . htmlspecialchars($label) . ': </td>';
					print '<td valign="top">';
					
					$this->displayPropertyValue($subject, $predicate, $object);
	
					print '</td>';
					print '</tr>';
				}
			}
			print '</table>';
		}
	
		print '</div>';
	}

	/**
	 * Display relations from and to an ORE aggregated resource.
	 * @param escape_repository_OreAggregation $aggregation The aggregation to which this object belongs
	 * @param escape_repository_OreAggregatedResource $aggregatedResource Aggregated Resource
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	public function displayOreObjectRelations(&$aggregation, &$aggregatedResource, $selectOnClickHandler = null)
	{
		// fetch the properties / relations of this resource (bidirectional)
		$aggregatedResourceProperties = $aggregatedResource->getProperties(true);
	
		$predicates = $aggregatedResourceProperties->getPredicates();
		
		// fetch relation annotations
		$relationAnnotationsUrisByTriple = array();
		foreach($predicates as $predicate)
		{
			if($predicate === 'http://purl.utwente.nl/ns/escape-annotations.owl#subjectOf' || $predicate === 'http://purl.utwente.nl/ns/escape-annotations.owl#objectOf')
			{
				$objects = $aggregatedResourceProperties->getObjects($predicate);
				foreach($objects as $object)
				{
					// this is a relation annotation, add these to a separate list
					$relationAnnotation = $this->repository->getOreAggregatedResourceByUri($object);
					$relationAnnotationProperties = $relationAnnotation->getProperties();
					$tripleSubject = $relationAnnotationProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#subject');
					$triplePredicate = $relationAnnotationProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#predicate');
					$tripleObject = $relationAnnotationProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#object');
					$triple = '<' . $tripleSubject . '> <' . $triplePredicate . '> <' . $tripleObject . '>'; 
					$relationAnnotationsUrisByTriple[$triple] = $object;
					
					$inverseTriplePredicate = $this->repository->repositorySchema->getInversePropertyUri($triplePredicate);
					if($inverseTriplePredicate !== null)
					{
						$inverseTriple = '<' . $tripleObject . '> <' . $inverseTriplePredicate . '> <' . $tripleSubject . '>'; 
						$relationAnnotationsUrisByTriple[$inverseTriple] = $object;
					}
				}
			}
		}
	//	print '<pre>' . htmlspecialchars(print_r($relationAnnotationsUrisByTriple, true)) . '</pre>';
		
		// find relations in the properties and order them by the predicate URI 
		$relatedObjectUrisByPredicate = array();
		foreach($predicates as $predicate)
		{
			if(!$this->repository->repositorySchema->isHiddenProperty($predicate))
			{
				$objects = $aggregatedResourceProperties->getObjects($predicate);
				foreach($objects as $object)
				{
					if(preg_match('~^info:fedora/' . $this->repository->config['data_object_namespace'] . ':~', $object))
					{
						$relatedObjectUrisByPredicate[$predicate][] = $object;
					}
				}
			}
		}
	//	print '<pre>' . htmlspecialchars(print_r($relatedObjectUrisByPredicate, true)) . '</pre>';
	
		// remove duplicate relations
		foreach($relatedObjectUrisByPredicate as $predicate => $relatedObjectUris)
		{
			$relatedObjectUrisByPredicate[$predicate] = array_unique($relatedObjectUris);
		}
	//	print '<pre>' . htmlspecialchars(print_r($relatedObjectUrisByPredicate, true)) . '</pre>';
		
		// fetch predicate range labels
		$predicateLabels = array();
		foreach(array_keys($relatedObjectUrisByPredicate) as $predicate)
		{
			$label = $this->repository->repositorySchema->getResourceLabel($predicate);
			$predicateLabels[$predicate] = $label;
		}
		asort($predicateLabels);
		
		// fetch objects
		$relatedObjectsByUri = array();
		foreach($relatedObjectUrisByPredicate as $predicate => $relatedObjectUris)
		{
			foreach($relatedObjectUris as $relatedObjectUri)
			{
				if(!isset($relatedObjectsByUri[$relatedObjectUri]))
				{
					if($predicate === "http://www.openarchives.org/ore/terms/isAggregatedBy")
					{
						$relatedObjectsByUri[$relatedObjectUri] = $this->repository->getOreAggregationByUri($relatedObjectUri);
					}
					else
					{
						$relatedObjectsByUri[$relatedObjectUri] = $this->repository->getOreAggregatedResourceByUri($relatedObjectUri);
					}
				}
			}
		}

		// display the related items
		if(count($predicateLabels) > 0)
		{
			print '<div class="escape_Relations">';
			$rowIdx = 0;
			foreach($predicateLabels as $predicate => $label)
			{
				print '<div class="escape_Category"><span title="' . htmlspecialchars($predicate) . '">' . htmlspecialchars($label) . '</span></div>';
		
				$relatedObjectUris = $relatedObjectUrisByPredicate[$predicate];
				
				// sort the related objects by title
				$relatedObjectTitlesByUri = array();
				foreach($relatedObjectUris as $relatedObjectUri)
				{
					$relatedObject = $relatedObjectsByUri[$relatedObjectUri];
					$relatedObjectTitlesByUri[$relatedObjectUri] = $relatedObject->getTitle();
				}
				// don't sort authors
				//TODO: this shouldn't be hard coded, add to repository schema? 
				if($predicate !== 'http://xmlns.com/foaf/0.1/maker')
				{
					asort($relatedObjectTitlesByUri, SORT_LOCALE_STRING);
				}
				
				foreach($relatedObjectTitlesByUri as $relatedObjectUri => $relatedObjectTitle)
				{
					$propertyLabel = $this->repository->repositorySchema->getResourceLabel($predicate);
					$triple = '<' . $aggregatedResource->getUri() . '> <' . $predicate . '> <' . $relatedObjectUri . '>'; 
					
					$relatedObject = $relatedObjectsByUri[$relatedObjectUri];
		
					print '<div class="escape_Relation">';
					
					if(isset($relationAnnotationsUrisByTriple[$triple]))
					{
						print ' <span class="escape_Actions"><a href="#" onclick="$(\'relationAnnotation' . $rowIdx . '\').toggle(); return false"><img src="/rsc/img/note.gif" alt="" /> View relation annotation</a></span>';

						print '<table style="display: none" class="escape_RelationAnnotation" id="relationAnnotation' . $rowIdx . '"><tr><td>';
						$relationAnnotation = $this->repository->getOreAggregatedResourceByUri($relationAnnotationsUrisByTriple[$triple]);
						$this->displayOreObject($aggregation, $relationAnnotation, false, 0, $selectOnClickHandler, false);
						print '</td></tr></table>';
					}
					
					$this->displayOreObject($aggregation, $relatedObject, false, 1, $selectOnClickHandler);
		
					print '</div>';
		
					$rowIdx++;
				}
		
			}
			print '</div>';
		}
	}

	/**
	 * Display second degree relations of an aggregated resource.
	 * @param escape_repository_OreAggregatedResource $aggregatedResource Aggregated resource
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	public function displaySecondDegreeRelatedResources($aggregatedResource, $selectOnClickHandler = null)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}
		
		$repositorySchema =& $this->repository->repositorySchema;
		
		// fetch second degree relations
		$relatedResources = $aggregatedResource->getSecondDegreeRelatedResources();

		if(count($relatedResources) > 0)
		{
			// calculate common parent classes
			$typeParentMap = array();
			foreach($relatedResources as $uri => $relatedResource)
			{
				$class = $relatedResource['type'];
				
				if(!isset($typeParentMap[$class]))
				{
					$typeParentMap[$class] = $class;
	
					$subClasses = $repositorySchema->getSubClassUriList($class);
					foreach($subClasses as $subClass)
					{
						$typeParentMap[$subClass] = $class;
					}
				}
			}
			
			// rearrange resources by common parent class
			$relatedResourcesByRootType = array();
			foreach($relatedResources as $uri => $relatedResource)
			{
				$relatedResourceTitlesByRootType[$typeParentMap[$relatedResource['type']]][$uri]= $relatedResource['title'];
			}
	
			// fetch labels for common parent classes
			$rootTypePluralLabels = array();
			foreach($relatedResourceTitlesByRootType as $rootType => $relatedResourceTitles)
			{
				$rootTypePluralLabels[$rootType] = $repositorySchema->getResourceLabelPlural($rootType);
			}
			
			// order the list by common parent class title
			asort($rootTypePluralLabels);
?>
<div class="escape_RelatedRelated">
<div><strong>Related items:</strong></div>
<br />
<?php
			// display the relations
			foreach($rootTypePluralLabels as $rootType => $rootTypePluralLabel)
			{
?>
<div><strong><?php print htmlspecialchars($rootTypePluralLabel); ?></strong></div>
<?php
				$relatedResourceTitles = $relatedResourceTitlesByRootType[$rootType];
				
				// sort the list of relations by title
				asort($relatedResourceTitles);
				
				foreach($relatedResourceTitles as $relatedResourceUri => $relatedResourceTitle)
				{
					$url = $this->getActionLinkByUri('show', $relatedResourceUri) . $gadgetModeUrlParameter;
?>
<div><a href="<?php print htmlspecialchars($url);?>" <?php if($selectOnClickHandler !== null) { ?>onclick="<?php print htmlspecialchars($selectOnClickHandler) . '(' . htmlspecialchars(json_encode( $this->repository->convertObjectUriToDataObjectId($relatedResourceUri) )) . '); return false'; ?>"<?php } ?> title="<?php print htmlspecialchars($relatedResourceTitle); ?> (<?php print htmlspecialchars($repositorySchema->getResourceLabel($relatedResources[$relatedResourceUri]['type'])); ?>)"><?php print htmlspecialchars($relatedResourceTitle); ?></a></div>
<?php
				}
?>
<br />
<?php
			}
?>
</div>
<?php
		}
	}
	
	/**
	 * Display a Relation Annotation.
	 * @param escape_repository_OreAggregation $aggregation The aggregation to which this object belongs
	 * @param escape_repository_OreAggregatedResource $aggregatedResource Aggregated Resource
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	public function displayRelationAnnotation(&$aggregation, &$aggregatedResource, $selectOnClickHandler = null, $addLinks = false)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}
		
		// fetch the properties / relations of this resource (bidirectional)
		$aggregatedResourceProperties = $aggregatedResource->getProperties();

		$subjectUri = $aggregatedResourceProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#subject');
		$predicateUri = $aggregatedResourceProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#predicate');
		$objectUri = $aggregatedResourceProperties->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#object');
		
		
		if($subjectUri === null || $predicateUri === null || $objectUri === null)
		{
			print 'ERROR: Invalid Relation Annotation object';
		}
		else
		{
			$subject = $this->repository->getOreObjectByUri($subjectUri);
			$object = $this->repository->getOreObjectByUri($objectUri);
			
			$predicateTitle = $this->repository->repositorySchema->getResourceLabel($predicateUri);
			
			// keep in sync with getTitle on escape_repository_OreAggregatedResource
			print 'On <em>"';
			
			if($addLinks)
			{
				if($selectOnClickHandler !== null)
				{
					print '<a href="/show/' . htmlspecialchars($subject->getId()) . $gadgetModeUrlParameter . '" onclick="' . htmlspecialchars($selectOnClickHandler) . '(' . htmlspecialchars(json_encode($subject->getId())) . '); return false">' . htmlspecialchars($subject->getTitle()) . '</a>';
				}
				else
				{
					print '<a href="/show/' . htmlspecialchars($subject->getId()) . $gadgetModeUrlParameter . '">' . htmlspecialchars($subject->getTitle()) . '</a>';
				}
			}
			else
			{
				print htmlspecialchars($subject->getTitle());
			}
			
			print '"</em> ';
			print htmlspecialchars(mb_strtolower($predicateTitle));
			print ' <em>"';
			
			if($addLinks)
			{
				if($selectOnClickHandler !== null)
				{
					print '<a href="/show/' . htmlspecialchars($object->getId()) . $gadgetModeUrlParameter . '" onclick="' . htmlspecialchars($selectOnClickHandler) . '(' . htmlspecialchars(json_encode($object->getId())) . '); return false">' . htmlspecialchars($object->getTitle()) . '</a>';
				}
				else
				{
					print '<a href="/show/' . htmlspecialchars($object->getId()) . $gadgetModeUrlParameter . '">' . htmlspecialchars($object->getTitle()) . '</a>';
				}
			}
			else
			{
				print htmlspecialchars($object->getTitle());
			}
			print '"</em>';
		}
	}
	
	/**
	 * Display a link to the aggregation.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 * @param escape_repository_OreAggregatedResource $aggregatedResource Aggregated Resource
	 */
	public function displayLinkToAggregationSection(&$aggregation, &$aggregatedResource, $selectOnClickHandler = null)
	{
		$gadgetModeUrlParameter = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
		}

		// fetch the title of the aggregation
		$aggregationRdfResource = $aggregation->getProperties();
		$aggregationTitle = $aggregationRdfResource->getFirstObject('http://purl.org/dc/terms/title');
		if($aggregationTitle === null)
		{
			$aggregationTitle = "Unnamed aggregation " . $aggregation->getId();
		}
	
		// display a link to the aggregation
?>
<div class="escape_Relations" style="float: right">
<em><a href="/show/<?php print htmlspecialchars($aggregation->getId()) . $gadgetModeUrlParameter; ?>" <?php if($selectOnClickHandler !== null) { ?>onclick="<?php print htmlspecialchars($selectOnClickHandler) . '(' . htmlspecialchars(json_encode($aggregation->getId())) . '); return false'; ?>"<?php } ?>><img src="/rsc/img/ore_logo_14.png" border="0" align="top" alt="" /> Part of the "<?php print htmlspecialchars($aggregationTitle); ?>" <?php print htmlspecialchars($this->repository->config['label_aggregation']); ?></a></em>
</div>
<?php
	}

	/**
	 * Display a link to create a new aggregated resource.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 */
	public function displayLinkToCreateAggregatedResource(&$aggregation)
	{
		if($this->user)
		{
			$userUri = $this->user->getUri();

			if($aggregation->hasOwner($userUri) || $aggregation->hasEditor($userUri) || $this->user->hasSystemRole(escape_repository_User::$role_administrator))
			{
				$containerElementId = $this->getUniqueId();
				$rangeUris = array('http://purl.utwente.nl/ns/escape-system.owl#Thing');
?>
<div class="escape_Section">
<script type="text/javascript">
oreObjectEditorAggregationUri = <?php print json_encode($aggregation->getUri()); ?>; 
</script>
<form method="post" action="#" onsubmit="return false">
<div id="link_<?php print htmlspecialchars($containerElementId); ?>">
<a href="#" onclick="$(<?php print htmlspecialchars(json_encode('actions_' . $containerElementId)); ?>).show(); $(<?php print htmlspecialchars(json_encode('link_' . $containerElementId)); ?>).hide(); new escape_escapeUi.OreAggregatedResourceCreator($(<?php print htmlspecialchars(json_encode('object_creator_' . $containerElementId)); ?>), <?php print htmlspecialchars(json_encode($rangeUris)); ?>); return false"><img src="/rsc/img/add.gif" style="vertical-align: top" border="0" alt="" /> Add a new object</a>
</div>
<div id="object_creator_<?php print htmlspecialchars($containerElementId); ?>"></div>
<div id="actions_<?php print htmlspecialchars($containerElementId); ?>" style="display: none">
<input type="hidden" name="value" value="" />
<input type="button" name="save" value="" onclick="escape_escapeUi.redirectToResourceByUri('edit', $($(this).form).select('[name=\'value\']')[0].value); return false" style="display: none" />
<button name="cancel" value="" onclick="$(<?php print htmlspecialchars(json_encode('object_creator_' . $containerElementId)); ?>).update(''); $(<?php print htmlspecialchars(json_encode('link_' . $containerElementId)); ?>).show(); $(<?php print htmlspecialchars(json_encode('actions_' . $containerElementId)); ?>).hide(); return false">Cancel</button>
</div>
</form>
</div>
<?php
			}
		}
	}
	
	/**
	 * Display an ORE aggregation.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 * @param array $rdfParentTypes Show only aggregated resources which are a (sub)class of the RDF types specified (optional
	 * @param boolean $displayFilter If set to true, the filters are shown (optional)
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	public function displayOreAggregation(&$aggregation, $rdfParentTypes = null, $displayFilter = null, $selectOnClickHandler = null)
	{
		$repositorySchema =& $this->repository->repositorySchema;
		
		$aggregationId = $aggregation->getId();
		$aggregationShowUrl = '/show/' . $aggregationId;
		
		// if a $rdfParentTypes filter is specified, fetch a list of all possible sub class RDF types 
		$rdfTypes = null;
		if($rdfParentTypes !== null)
		{
			$rdfTypes = array();
			foreach($rdfParentTypes as $rdfParentType)
			{
				$rdfTypes[] = $rdfParentType;
				$subClassUriList = $repositorySchema->getSubClassUriList($rdfParentType);
				foreach($subClassUriList as $subClassUri)
				{
					$rdfTypes[] = $subClassUri;
				}
			}	
			$rdfTypes = array_unique($rdfTypes);			
		}
		
		// fetch the aggregated resources
		$aggregatedResources = $aggregation->getAggregatedResources($rdfTypes);

		if(count($aggregatedResources) > 0)
		{
			// organize the aggregated resources by their non system root class labels
			$aggregatedResourcesByRootClassLabel = array();
			foreach($aggregatedResources as $aggregatedResource)
			{
				$classUris = $aggregatedResource->getRdfTypes();
				$rootClassUris = array();
				foreach($classUris as $classUri)
				{ 
					$rootClassUris[] = $repositorySchema->getEscapeRootClassUri($classUri); 
				}
				$rootClassUris = array_unique($rootClassUris);
				foreach($rootClassUris as $rootClassUri)
				{ 
					$label = $repositorySchema->getResourceLabelPlural($rootClassUri);
					$aggregatedResourcesByRootClassLabel[$label][] = $aggregatedResource;
				}
			}
			ksort($aggregatedResourcesByRootClassLabel);
	
			// build a list of root class types for the filter selector
			$rootClassUris = $aggregation->getAggregatedResourceEscapeRootClassUris();
			$rootClassLabelsByUri = array();
			foreach($rootClassUris as $rootClassUri)
			{
				$rootClassLabelsByUri[$rootClassUri] = $repositorySchema->getResourceLabelPlural($rootClassUri);
			}
			asort($rootClassLabelsByUri);
?>
<div class="escape_Relations">
<div class="escape_SectionTitle">Aggregated resources</div>
<?php
			if($displayFilter && count($rootClassLabelsByUri) > 1)
			{
				$filterLinks = array();
				if($rdfTypes !== null && count($rdfTypes) > 0)
				{
					$filterLinks[] = '<a href="' . htmlspecialchars($aggregationShowUrl) . '">All</a>';
				}
				else
				{
					$filterLinks[] = '<strong>All</strong>';
				}
				foreach($rootClassLabelsByUri as $rootClassUri => $rootClassLabel)
				{
					if($rdfTypes !== null && in_array($rootClassUri, $rdfTypes))
					{
						$filterLinks[] = '<strong>' . htmlspecialchars($rootClassLabel) . '</strong>';
					}
					else
					{
						$filterUrl = $aggregationShowUrl . '?filter=' . rawurlencode($rootClassUri);
						$filterLinks[] = '<a href="' . htmlspecialchars($filterUrl) . '">' . htmlspecialchars($rootClassLabel) . '</a>';
					}
				}
				print '<div class="escape_Filter">Filter: ' . implode(' - ', $filterLinks) . '</div>';
			}
			
			foreach($aggregatedResourcesByRootClassLabel as $rootClassLabel => $aggregatedResources)
			{
				usort($aggregatedResources, array('escape_repository_DataObject', '_compareTitle'));
				
				print '<div class="escape_Category">' . htmlspecialchars($rootClassLabel) . '</div>';
			
				foreach($aggregatedResources as $aggregatedResource)
				{
					print '<div class="escape_Relation">';
					$this->displayOreObject($aggregation, $aggregatedResource, false, 1, $selectOnClickHandler);
					print '</div>';
				}
			}
?>
</div>
<?php
		}
	}
	
	/**
	 * Display the page content for an aggregation or aggregated resource.
	 * @param escape_repository_OreAggregation $aggregation Aggregation
	 * @param escape_repository_OreAggregatedResource $aggregatedResource Aggregated resource or null if no aggregated resource is selected
	 * @param array $filter Array of RDF type URI's to filter the aggregation overview (optional) 
	 * @param boolean $displayGraph If set to true, the graph is shown (optional)
	 * @param boolean $displayFilter If set to true, the filters are shown (optional)
	 * @param string $selectOnClickHandler JavaScript onClick handler for "select" actions (optional)
	 */
	function displayObjectPageFragment($aggregation, $aggregatedResource, $filter = null, $displayGraph = false, $displayFilter = false, $selectOnClickHandler = null)
	{
		$gadgetModeUrlParameter = '';
		$gadgetModeTargetBlankAttribute = '';
		if($this->isGadgetMode())
		{
			$gadgetModeUrlParameter = '?gadget=1';
			$gadgetModeTargetBlankAttribute = 'target="_blank"';
		}
		
		$oreObject = null;
		
		if($aggregatedResource !== null)
		{
			$oreObject =& $aggregatedResource;
			
			// this is an aggregated resource
			
			// display a link to the aggregation
			$this->displayLinkToAggregationSection($aggregation, $aggregatedResource, $selectOnClickHandler);
			
			// display graph toggle
			if($displayGraph)
			{
				print '<div class="escape_Section"><a href="/show/' . rawurlencode($aggregatedResource->getId()) . $gadgetModeUrlParameter . '">Hide graph</a></div>';
			}
			else
			{
				print '<div class="escape_Section"><a ' . $gadgetModeTargetBlankAttribute . ' href="/graph/' . rawurlencode($aggregation->getId()) . '#id=' . rawurlencode($aggregatedResource->getId()) . '">Show graph</a></div>';
			}
			
?>
<div class="escape_Selection">
<?php
			// display the selected resource and it's properties
			$this->displayOreObject($aggregation, $aggregatedResource, true, 0, $selectOnClickHandler);
?>
</div>
<?php
			
			// display second degree related resources		
			$this->displaySecondDegreeRelatedResources($aggregatedResource, $selectOnClickHandler);		
	
			// display the relations of the selected resource
			$this->displayOreObjectRelations($aggregation, $aggregatedResource, $selectOnClickHandler);
		}
		else
		{
			// this is an aggregation
			$oreObject =& $aggregation;
			
			// display a link to create a new aggregated resource
			//print '<div style="float: right">';
			$this->displayLinkToCreateAggregatedResource($aggregation);
			//print '</div>';
			
			// display graph toggle
			if($displayGraph)
			{
				print '<div class="escape_Section"><a href="/show/' . rawurlencode($aggregation->getId()) . $gadgetModeUrlParameter . '">Hide graph</a></div>';
			}
			else
			{
				print '<div class="escape_Section"><a ' . $gadgetModeTargetBlankAttribute . ' href="/graph/' . rawurlencode($aggregation->getId()) . '#id=' . rawurlencode($aggregation->getId()) . '">Show graph</a></div>';
			}
?>
<div class="escape_Selection">
<?php
			// display the selected aggregation and its properties
			$this->displayOreObject($aggregation, $aggregation, true, 0, $selectOnClickHandler);
?>
</div>
<?php
			// display the resources aggregated by this aggregation
			$this->displayOreAggregation($aggregation, $filter, $displayFilter, $selectOnClickHandler);
		}
		
		$rdfResource = $oreObject->getProperties();
		$createdBy = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#createdBy');
		$lastModifiedBy = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-system.owl#lastModifiedBy');

?>
<div class="escape_ObjectMetaDetails">
<?php
		if($createdBy !== null && $createdBy !== '')
		{
?>
<div>
Created: 
<?php print htmlspecialchars(gmdate('j M Y', strtotime($oreObject->getCreatedDate()))); ?> 
by 
<?php print htmlspecialchars($createdBy); ?>
</div>
<?php
		}
		if($lastModifiedBy !== null && $lastModifiedBy !== '')
		{
?>
<div>
Last modified: 
<?php print htmlspecialchars(gmdate('j M Y', strtotime($oreObject->getLastModifiedDate()))); ?> 
by 
<?php print htmlspecialchars($lastModifiedBy); ?>
</div>
<?php
		}
?>
<div>
<a target="_blank" href="<?php print htmlspecialchars($aggregation->getRemExternalUri()); ?>">RDF/XML</a>
</div>
</div>
<?php
	}

	/**
	 * Display the OpenID login form
	 * @param boolean $onSuccess Action to perform on success
	 * @param boolean $onFailure Action to perform on failure
	 * @param array $parameters Parameters
	 */
	public function displayOpenIdLoginForm($onSuccess, $onFailure, $parameters = array())
	{
?>
<form method="get" action="/beginOpenIdLogin">
<?php
		if($this->isGadgetMode())
		{
?>
<input type="hidden" name="gadget" value="1" />
<?php
		}
?>
<input type="hidden" name="onSuccess" value="<?php print htmlspecialchars($onSuccess)?>" />
<input type="hidden" name="onFailure" value="<?php print htmlspecialchars($onFailure)?>" />
<?php
		foreach($parameters as $key => $value)
		{
?>
<input type="hidden" name="<?php print htmlspecialchars($key); ?>" value="<?php print htmlspecialchars($value); ?>" />
<?php
		}
?>

<button onclick="$('custom_openid').toggle(); $(this).form.openid.value = ''; return false">OpenID URL</button>
<button onclick="$('custom_openid').hide(); $(this).form.openid.value = 'https://www.google.com/accounts/o8/id'">Google</button>
<button onclick="$('custom_openid').hide(); $(this).form.openid.value = 'yahoo.com'">Yahoo</button>
<button onclick="$('custom_openid').hide(); $(this).form.openid.value = 'hyves.net'">Hyves</button>

<p id="custom_openid" style="display: none">
OpenID:
<input type="text" name="openid" value="<?php print htmlspecialchars($_REQUEST['openid']); ?>" style="background: #FFFFFF url('/rsc/img/openid-icon-small.gif') no-repeat scroll 0pt 50%; padding-left: 18px;" />
<input type="submit" value="Log in" />
</p>

<p class="escape_Note">
<a target="_blank" href="http://openid.net/get/">How do I get an OpenID?</a>
</p>

</form>
<?php
	}
	
	public function displayInfoTooltip($content)
	{
		$id = $this->getUniqueId();
?><span id="<?php print htmlspecialchars($id); ?>"></span><script type="text/javascript">
$(<?php print json_encode($id)?>).update(escape_escapeUi.createInfoTooltip(<?php print json_encode($content)?>));
</script><?php
	}
}